defmodule PlausibleWeb.Live.Shields.IPRules do
  @moduledoc """
  LiveView allowing IP Rules management
  """

  use PlausibleWeb, :live_component

  alias PlausibleWeb.Live.Components.PrimaModal
  alias Plausible.Shields
  alias Plausible.Shield

  def update(assigns, socket) do
    socket =
      socket
      |> assign(
        ip_rules_count: assigns.ip_rules_count,
        remote_ip: assigns.remote_ip,
        site: assigns.site,
        current_user: assigns.current_user,
        form: new_form()
      )
      |> assign_new(:ip_rules, fn %{site: site} ->
        Shields.list_ip_rules(site)
      end)

    {:ok, socket}
  end

  def render(assigns) do
    ~H"""
    <div>
      <.settings_tiles>
        <.tile docs="excluding">
          <:title>IP block list</:title>
          <:subtitle :if={not Enum.empty?(@ip_rules)}>
            Reject incoming traffic from specific IP addresses.
          </:subtitle>

          <%= if Enum.empty?(@ip_rules) do %>
            <div class="flex flex-col items-center justify-center pt-5 pb-6 max-w-md mx-auto">
              <h3 class="text-center text-base font-medium text-gray-900 dark:text-gray-100 leading-7">
                Block an IP address
              </h3>
              <p class="text-center text-sm mt-1 text-gray-500 dark:text-gray-400 leading-5 text-pretty">
                Reject incoming traffic from specific IP addresses.
                <.styled_link href="https://plausible.io/docs/excluding" target="_blank">
                  Learn more
                </.styled_link>
              </p>
              <.button
                :if={@ip_rules_count < Shields.maximum_ip_rules()}
                id="add-ip-rule"
                phx-click={Prima.Modal.JS.open("ip-rule-form-modal")}
              >
                Add IP address
              </.button>
            </div>
          <% else %>
            <.filter_bar :if={@ip_rules_count < Shields.maximum_ip_rules()} filtering_enabled?={false}>
              <.button
                id="add-ip-rule"
                phx-click={Prima.Modal.JS.open("ip-rule-form-modal")}
                mt?={false}
              >
                Add IP address
              </.button>
            </.filter_bar>

            <.notice
              :if={@ip_rules_count >= Shields.maximum_ip_rules()}
              class="mt-4"
              title="Maximum number of addresses reached"
              theme={:gray}
            >
              <p>
                You've reached the maximum number of IP addresses you can block ({Shields.maximum_ip_rules()}). Please remove one before adding another.
              </p>
            </.notice>

            <div class="mt-6">
              <.table rows={@ip_rules}>
                <:thead>
                  <.th>IP address</.th>
                  <.th hide_on_mobile>Status</.th>
                  <.th hide_on_mobile>Description</.th>
                  <.th invisible>Actions</.th>
                </:thead>
                <:tbody :let={rule}>
                  <.td max_width="max-w-40">
                    <div class="flex items-center truncate">
                      <span
                        :if={to_string(rule.inet) == @remote_ip}
                        class="inline-flex items-center gap-x-1.5 rounded-md px-2 mr-2 py-1 text-xs font-medium text-gray-700 dark:text-white ring-1 ring-inset ring-gray-300 dark:ring-gray-700"
                      >
                        <svg class="h-1.5 w-1.5 fill-green-400" viewBox="0 0 6 6" aria-hidden="true">
                          <circle cx="3" cy="3" r="3" />
                        </svg>
                        YOU
                      </span>
                      <span
                        id={"inet-#{rule.id}"}
                        class="cursor-help"
                        title={"Added at #{format_added_at(rule.inserted_at, @site.timezone)} by #{rule.added_by}"}
                      >
                        {rule.inet}
                      </span>
                    </div>
                  </.td>
                  <.td hide_on_mobile>
                    <span :if={rule.action == :deny}>
                      Blocked
                    </span>
                    <span :if={rule.action == :allow}>
                      Allowed
                    </span>
                  </.td>
                  <.td hide_on_mobile truncate>
                    <span :if={rule.description} title={rule.description}>
                      {rule.description}
                    </span>
                    <span :if={!rule.description} class="text-gray-400 dark:text-gray-600">
                      --
                    </span>
                  </.td>
                  <.td actions>
                    <.delete_button
                      id={"remove-ip-rule-#{rule.id}"}
                      phx-target={@myself}
                      phx-click="remove-ip-rule"
                      phx-value-rule-id={rule.id}
                      data-confirm="Are you sure you want to revoke this rule?"
                    />
                  </.td>
                </:tbody>
              </.table>
            </div>
          <% end %>

          <PrimaModal.modal id="ip-rule-form-modal">
            <div class="p-5 sm:p-6 max-w-md">
              <div class="hidden sm:block absolute top-0 right-0 pt-4 pr-4">
                <button
                  phx-click={Prima.Modal.JS.close()}
                  class="text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400"
                >
                  <span class="sr-only">Close</span>
                  <Heroicons.x_mark class="size-6" />
                </button>
              </div>
              <div class="flex flex-col gap-y-4 text-center sm:text-left">
                <PrimaModal.modal_title>
                  Add IP to block list
                </PrimaModal.modal_title>
                <.form
                  :let={f}
                  for={@form}
                  phx-submit="save-ip-rule"
                  phx-target={@myself}
                >
                  <div>
                    <p
                      :if={not ip_rule_present?(@ip_rules, @remote_ip)}
                      class="text-sm text-gray-500 dark:text-gray-400 mb-4"
                    >
                      Your current IP address is: <span class="font-mono"><%= @remote_ip %></span>.
                      <.styled_link phx-target={@myself} phx-click="prefill-own-ip-rule">
                        Click here
                      </.styled_link>
                      to block your own traffic, or enter a custom address below.
                    </p>

                    <.input
                      data-autofocus
                      field={f[:inet]}
                      label="IP address"
                      placeholder="e.g. 192.168.127.12"
                    />
                  </div>

                  <.input
                    field={f[:description]}
                    label="Description (optional)"
                    placeholder="e.g. The Office"
                  />

                  <p class="mt-4 text-sm text-gray-500 dark:text-gray-400">
                    Once added, we will start rejecting traffic from this IP within a few minutes.
                  </p>
                  <.button type="submit" class="w-full">
                    Add IP address
                  </.button>
                </.form>
              </div>
            </div>
          </PrimaModal.modal>
        </.tile>
      </.settings_tiles>
    </div>
    """
  end

  def handle_event("prefill-own-ip-rule", %{}, socket) do
    form =
      %Plausible.Shield.IPRule{}
      |> Plausible.Shield.IPRule.changeset(%{
        inet: socket.assigns.remote_ip,
        description: socket.assigns.current_user.name
      })
      |> to_form()

    {:noreply, assign(socket, form: form)}
  end

  def handle_event("save-ip-rule", %{"ip_rule" => params}, socket) do
    user = socket.assigns.current_user

    case Shields.add_ip_rule(
           socket.assigns.site.id,
           params,
           added_by: user
         ) do
      {:ok, rule} ->
        socket =
          socket
          |> Prima.Modal.push_close("ip-rule-form-modal")
          |> assign(
            form: new_form(),
            ip_rules: [rule | socket.assigns.ip_rules],
            ip_rules_count: socket.assigns.ip_rules_count + 1
          )

        send_flash(
          :success,
          "IP rule added successfully. Traffic will be rejected within a few minutes."
        )

        {:noreply, socket}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign(socket, form: to_form(changeset))}
    end
  end

  def handle_event("remove-ip-rule", %{"rule-id" => rule_id}, socket) do
    Shields.remove_ip_rule(socket.assigns.site.id, rule_id)

    send_flash(
      :success,
      "IP rule removed successfully. Traffic will be resumed within a few minutes."
    )

    {:noreply,
     socket
     |> assign(
       ip_rules_count: socket.assigns.ip_rules_count - 1,
       ip_rules: Enum.reject(socket.assigns.ip_rules, &(&1.id == rule_id))
     )}
  end

  def send_flash(kind, message) do
    send(self(), {:flash, kind, message})
  end

  defp new_form() do
    %Shield.IPRule{}
    |> Shield.IPRule.changeset(%{})
    |> to_form()
  end

  defp ip_rule_present?(rules, ip) do
    not is_nil(Enum.find(rules, &(to_string(&1.inet) == ip)))
  end

  defp format_added_at(dt, tz) do
    dt
    |> Plausible.Timezones.to_datetime_in_timezone(tz)
    |> Calendar.strftime("%Y-%m-%d %H:%M:%S")
  end
end
